'''RWBWindow - A Robot Framework Workbench window class'''

import os
import wx
import firebrick
import rwb
import platform
import robot
import wx.lib.agw.aui as aui
import webbrowser
import pkgutil
from WelcomePanel import WelcomePanel
from EditToolbar import EditToolBar
from RobotToolBar import RobotToolBar
from PerspectiveToolBar import PerspectiveToolBar
from statusbar import CustomStatusBar

ID_OPEN_FILE = wx.NewId()
ID_OPEN_DIRECTORY = wx.NewId()
ID_ROBOT_VALIDATION = wx.NewId()
ID_BROWSE_MODE = wx.NewId()
ID_EDIT_MODE = wx.NewId()
ID_HELP_WEBSITE = wx.NewId()

VERSION = rwb.__version__
NAME="RWB"
TITLE="Robot Framework Workbench"
PROJECT_URL = "http://code.google.com/p/robotframework-workbench/"


class RWBWindow(firebrick.FirebrickWindow):
    '''Main window for RWB'''

    def __init__(self, *args, **kwargs):
        firebrick.FirebrickWindow.__init__(self, *args, **kwargs)

        self._app.Register(WelcomePanel)
        self._app.Register(EditToolBar)
        self._app.Register(RobotToolBar)
        self._app.Register(PerspectiveToolBar)

        self.CreateMenuBar()
        self.CreateStatusBar()

        self.ShowInitialPerspective()

        try:
            self.NewPerspective("Edit Mode", ("rwb.core.navigator", "rwb.core.edit_notebook", 
                                              "rwb.toolbar.edit", "rwb.toolbar.robot"), protected=True)
        except firebrick.PerspectiveAlreadyExistsError, e:
            pass

        try:
            self.NewPerspective("Browse Mode", ("rwb.core.navigator", "rwb.core.suite-browser", 
                                                "rwb.toolbar.edit", "rwb.toolbar.robot"), protected=True)
        except firebrick.PerspectiveAlreadyExistsError, e:
            pass

        self._app.Subscribe("open", self.OnOpen)

        # set up bindings for common editing functions
        # FIXME: this seems to cause the callbacks to be
        # called a kajillion times in spite of the 
        # SetUpdateInterval value
        wx.UpdateUIEvent.SetUpdateInterval(1000)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
#        wx.EVT_UPDATE_UI(self, wx.ID_REDO, self.OnUpdateRedo)
#        wx.EVT_UPDATE_UI(self, wx.ID_UNDO, self.OnUpdateUndo)
#        wx.EVT_UPDATE_UI(self, wx.ID_CUT, self.OnUpdateCut)
#        wx.EVT_UPDATE_UI(self, wx.ID_COPY, self.OnUpdateCopy)
#        wx.EVT_UPDATE_UI(self, wx.ID_PASTE, self.OnUpdatePaste)

    def CreateStatusBar(self):
        self.sb = CustomStatusBar(self)
        self.SetStatusBar(self.sb)

    def CreateDefaultPerspective(self, name):
        if name == "robot":
            name = "Robot"
            if name not in self._app.perspectives:
                self.NewPerspective(name, ("rwb.core.navigator", "rwb.core.edit_notebook"))

    def ShowInitialPerspective(self):
        if not self._app.perspectives:
            self.NewPerspective("Welcome!", 
                                ("rwb.panel.welcome",))
        self.LoadPerspective(self._app.perspectives[0].id)
        self.auimgr.Update()
        
    def OnOpen(self, event):
        pass

    def CreateMenuBar(self):
        '''Creates the menubar for the window'''
        menubar = wx.MenuBar()

        fileMenu = wx.Menu()
        # yeah, ART_REPORT_VIEW instead of FILE_OPEN; FILE_OPEN looks like 
        # a folder, not a file. 
#        fileMenu.Append(wx.ART_REPORT_VIEW, "&Open File Suite")
        fileMenu.Append(ID_OPEN_FILE, "&Open File")
        fileMenu.Append(ID_OPEN_DIRECTORY, "Open Test Suite &Directory")
        fileMenu.AppendSeparator()
        fileMenu.Append(wx.ID_SAVE)
        fileMenu.AppendSeparator()
        fileMenu.Append(firebrick.ID_SHOW_PREFERENCES, "&Preferences")
        fileMenu.AppendSeparator()
        fileMenu.Append(wx.ID_EXIT, "&Quit")

        editMenu = wx.Menu()
        editMenu.Append(wx.ID_CUT)
        editMenu.Append(wx.ID_COPY)
        editMenu.Append(wx.ID_PASTE)
        editMenu.AppendSeparator()
        editMenu.Append(wx.ID_UNDO)
        editMenu.Append(wx.ID_REDO)

        self.editMenu = editMenu

        viewMenu = wx.Menu()
        panelsMenu = wx.Menu()
        toolbarsMenu = wx.Menu()
        viewMenu.Append(ID_BROWSE_MODE, "Browse Mode")
        viewMenu.Append(ID_EDIT_MODE, "Edit Mode")
        viewMenu.AppendSeparator()
        viewMenu.AppendMenu(wx.ID_ANY, "Panels", panelsMenu)
        viewMenu.AppendMenu(wx.ID_ANY, "Toolbars", toolbarsMenu)
        self.viewMenu = viewMenu

        perspectiveMenu = wx.Menu()
        perspectiveMenu.Append(firebrick.ID_SAVE_VIEW_AS, "New Perspective...")
        perspectiveMenu.Append(firebrick.ID_DELETE_CURRENT_PERSPECTIVE, 
                               "Delete Current Perspective")
        perspectiveMenu.AppendSeparator()

        helpMenu = wx.Menu()
        helpMenu.Append(wx.ID_HELP, "Help using Robot Framework Workbench")
        helpMenu.Append(ID_HELP_WEBSITE, "Visit Project Website")
        helpMenu.AppendSeparator()
        helpMenu.Append(wx.ID_ABOUT, "About")

        menubar.Append(fileMenu, "&File")
        menubar.Append(editMenu, "&Edit")
        menubar.Append(viewMenu, "&View")
        menubar.Append(perspectiveMenu, "&Perspective")
        
        # I should add a super-secret binding to make this
        # appear on demand. <escape><escape> maybe?
        foo = wx.NewId()
        self.Bind(wx.EVT_MENU, self.OnDebugAccelerator, id=foo)
        accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, wx.WXK_F1, foo)])

        if bool(os.environ.get("RWB_DEBUG", False)):
            debugMenu = self.AddDebugMenu(menubar)
        menubar.Append(helpMenu, "&Help")

        self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnViewChanged)
        self.Bind(aui.EVT_AUI_PANE_RESTORE, self.OnViewChanged)
        self.Bind(aui.EVT_AUI_PERSPECTIVE_CHANGED, self.OnViewChanged)

        wx.EVT_MENU(self, ID_HELP_WEBSITE, self.OnHelpWebsite)
        wx.EVT_MENU(self, wx.ID_HELP, self.OnHelp)
        wx.EVT_MENU(self, ID_EDIT_MODE, self.OnEditMode)
        wx.EVT_MENU(self, ID_BROWSE_MODE, self.OnBrowseMode)
        wx.EVT_MENU(self, wx.ID_ABOUT, self.OnHelpAbout)
        wx.EVT_MENU(self, wx.ID_COPY, self.OnCopy)
        wx.EVT_MENU(self, wx.ID_CUT, self.OnCut)
        wx.EVT_MENU(self, wx.ID_PASTE, self.OnPaste)
#        wx.EVT_MENU(self, ID_ROBOT_VALIDATION, self.OnQC)

        wx.EVT_MENU(self, wx.ID_SAVE, self.OnFileSave)
        wx.EVT_MENU(self, ID_OPEN_FILE, self.OnFileOpen)
        wx.EVT_MENU(self, ID_OPEN_DIRECTORY, self.OnFileOpen)
        wx.EVT_MENU(self, wx.ID_EXIT, self.OnExit)

        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.menubar = menubar
        self.statusbar = self.GetStatusBar()
        self.SetMenuBar(menubar)
        self.panelsMenu = panelsMenu
        self.toolbarsMenu = toolbarsMenu
        self.perspectiveMenu = perspectiveMenu

    def OnHelp(self, event):
        '''Display a help window'''
        # FIXME: gracefully handle the case where we
        # can't find the help file...
        hw = wx.GetApp().GetHelpWindow()
        hw.SetTitle("Robot Framework Workbench - Help")
        html = pkgutil.get_data("rwb", "doc/help.html")
        hw.SetPage(html)
        hw.Show()
        hw.Raise()

    def OnHelpWebsite(self, event):
        webbrowser.open(PROJECT_URL)

    def OnEditMode(self, event):
        self.LoadPerspectiveByName("Edit Mode")

    def OnBrowseMode(self, event):
        self.LoadPerspectiveByName("Browse Mode")

    def OnDebugAccelerator(self, event):
        print "got it!"

    def OnExit(self, event):
        self.SaveCurrentPerspective()
        self.Destroy()
        event.Skip()

    def OnClose(self, event):
        # this doesn't seem to work; is it because the view has
        # already been destroyed? Duplicate the non-workness by
        # removing a panel from the default view the click 
        # on the [x] on the titlebar to exit. The view change
        # has not been saved.
        self.SaveCurrentPerspective()
        event.Skip()

    def OnHelpAbout(self, event):
        info = wx.AboutDialogInfo()
        info.Name = NAME
        info.Version = VERSION
        info.Description = TITLE
        info.Description += "\npython version %s" % platform.python_version()
        info.Description += "\nwxPython version %s" % wx.version()
        info.Description += "\nRobot framework version %s" % robot.get_version()
        try:
            import rsa
            info.Description += "\nRSA version %s" % rsa.__version__
        except:
            pass

        for plugin in self._app.plugin_manager.plugins:
            # we need some sort of interface so that a plugin
            # can provide information to the dialog. Maybe
            # plugin.help_about?
            pass
        wx.AboutBox(info)
        
    # def OnQC(self, event):
    #     print "Got an OnQC event..."
    #     # get the current tab, get its editor, if it has a QC method,
    #     # run it
    #     edit_panel = self._app.GetPanel(self, "rwb.core.edit_notebook")
    #     page = edit_panel.GetCurrentEditor()
    #     print "...", page
    #     page.QC()
#        for i in range(self._notebook.GetPageCount()):
#            page = self._notebook.GetPage(i)


    def GetCurrentEditor(self):
        '''Return current editor

        Current editor will be none if the editor panel is not
        visible, or there are no tabs open in the editor panel.
        '''
        editor_notebook = self.GetPanel("rwb.core.edit_notebook")
        if editor_notebook and editor_notebook.IsShown():
            return editor_notebook.GetCurrentEditor()
        return None
        
    def OnUpdateUI(self, event):
        event_id = event.GetId()

        # These are for generic editing features that can be 
        # handled by the widget with focus
        focus_mapping = {wx.ID_CUT: "CanCut",
                         wx.ID_COPY: "CanCopy",
                         wx.ID_PASTE: "CanPaste",
                         wx.ID_UNDO: "CanUndo",
                         wx.ID_REDO: "CanRedo",
                   }
        # these are for features that can be handled by 
        # the current editor 
        editor_mapping = {
            wx.ID_SAVE: "NeedsSave",
            }
        
        if event_id in focus_mapping:
            target = wx.Window.FindFocus()
            func_name = focus_mapping[event_id]
            event.Enable(target is not None and
                         hasattr(target, func_name) and
                         getattr(target, func_name)())

        elif event_id in editor_mapping:
            target = self.GetCurrentEditor()
            func_name = editor_mapping[event_id]
            event.Enable(target is not None and
                         hasattr(target, func_name) and
                         getattr(target, func_name)())
        else:
            event.Skip()

    # the following functions work based on the assumption
    # that the accelerators and menu items and toolbar items
    # will only be enabled if the window with focus supports 
    # the given function
    def OnCut(self, event): wx.Window.FindFocus().Cut()
    def OnCopy(self, event): wx.Window.FindFocus().Copy()
    def OnPaste(self, event): wx.Window.FindFocus().Paste()
    def OnUndo(self, event): wx.Window.FindFocus().Undo()
    def OnRedo(self, event): wx.Window.FindFocus().Redo()

    # def OnOpen(self, message):
    #     data_object = message.data["data_object"]
    #     self.SetTitle("%s - RWB: The Robot Framework Workbench" % data_object.path)
        
    def OnFileSave(self, event):
        edit_panel = self._app.GetPanel(self, "rwb.core.edit_notebook")
        edit_panel.Save()

    def OnFileOpen(self, event):
        if event.GetId() == ID_OPEN_FILE:
            dialog = wx.FileDialog(self, 
                                   message="Choose a test suite file",
                                   defaultDir = os.getcwd(),
                                   defaultFile="",
                                   style = wx.FD_OPEN
                                   )
                
        else:
            dialog = wx.DirDialog(self, 
                                   message="Choose a test suite directory",
                                   style = wx.DD_DIR_MUST_EXIST,
                                   )
        if dialog.ShowModal() == wx.ID_OK:
            path = dialog.GetPath()
            self._app.Open(path)
        
    def OnViewChanged(self, event):
        self._schedule_job(self._update_perspective_menu)
        event.Skip()

    def GetPanel(self, panel_id, create=True):
        panel = self._app.GetPanel(self, panel_id, create=create)
        return panel

    def ShowPanels(self, *args):
        '''Make sure one or more panels are visible

        If the panel should be shown inside some other container,
        make sure that container is visible then ask it to display
        the panel.
        '''
        for panel_id in args:
            panel = self._app.GetPanel(self, panel_id, create=True)
            container_id = getattr(panel, "container", None)
            if container_id is not None:
                container = self._app.GetPanel(self, container_id, create=True)
                container.ShowPanel(panel)
            else:
                self.ShowPanel(panel)

    def ShowPanel(self, panel):
        '''Make the given panel visible in the current perspective'''
        self.auimgr.GetPane(panel).Show()
        self.auimgr.Update()

    def ShowToolbars(self, *args):
        '''Make sure one or more toolbars are visible'''
        update_required = False
        for toolbar_id in args:
            toolbar = self._app.GetToolbar(self, toolbar_id, create=True)
            if not toolbar.IsShown():
                self.auimgr.GetPane(toolbar).Show()
                update_required = True
        if update_required:
            self.auimgr.Update()

    def DeleteCurrentPerspective(self):
        '''Deletes the current perspective

        This will silently fail if there is only one perspective
        '''
        if self.CanDeleteCurrentPerspective():
            current = self._app.perspectives.pop(0)
            settings = self._app.GetSettings("perspectives")
            del settings[current.name]
            self.perspectiveMenu.Delete(current.id)
            self.LoadPerspective(self._app.perspectives[0].id)
            
    def LoadPerspective(self, id):
        super(RWBWindow, self).LoadPerspective(id)
        self.StatusBar.SetPerspective(self._app.perspectives[0].name)

    def _update_perspective_menu(self):
        '''Update the perspective menu based on current state'''
        for perspective in self._app.perspectives:
            item_id = perspective.id
            item = self.perspectiveMenu.FindItemById(item_id)
            if item:
                self.perspectiveMenu.Delete(item_id)

        self.perspectiveMenu.Enable(firebrick.ID_DELETE_CURRENT_PERSPECTIVE,
                                    self.CanDeleteCurrentPerspective())

        for perspective in sorted(self._app.perspectives, key=lambda x: x.name):
            self.perspectiveMenu.AppendCheckItem(perspective.id, perspective.name)
            wx.EVT_MENU(self, perspective.id, self.OnChangePerspective)
            if perspective == self._app.perspectives[0]:
                self.perspectiveMenu.Check(perspective.id, True)
            else:
                self.perspectiveMenu.Check(perspective.id, False)

        id_pool = []
        for item in self.panelsMenu.GetMenuItems():
            id_pool.append(item.GetId())
            self.panelsMenu.DeleteItem(item)

        # this doens't take into account the enabled state of the
        # plugin from whence the panel comes. Does that matter?

        for panel_class in sorted(firebrick.BasePanel.__subclasses__(), 
                                  key=lambda x: x.name):
            id = id_pool.pop() if len(id_pool) else wx.NewId()
            wx.EVT_MENU(self, id, lambda event, 
                        _id = panel_class._id: self.OnPanelMenu(event, _id))
            self.panelsMenu.AppendCheckItem(id, panel_class.name)
            panel = self._app.GetPanel(self, panel_class._id, create=False)
            if panel is not None and panel.container is not None:
                container = self.GetPanel(panel.container)
                self.panelsMenu.Check(id, container.IsShown(panel))
            else:
                self.panelsMenu.Check(id, panel is not None and panel.IsShown())

        for item in self.toolbarsMenu.GetMenuItems():
            id_pool.append(item.GetId())
            self.toolbarsMenu.DeleteItem(item)

        for toolbar_class in sorted(aui.AuiToolBar.__subclasses__(),
                                    key=lambda x: x.name):
            _id = id_pool.pop() if len(id_pool) else wx.NewId()
            self.toolbarsMenu.AppendCheckItem(_id, toolbar_class.name)
            wx.EVT_MENU(self, _id, lambda event, 
                        _id = toolbar_class._id: self.OnToolBarMenu(event, _id))
            toolbar = self._app.GetToolbar(self, toolbar_class._id, create=False)
            self.toolbarsMenu.Check(_id, toolbar is not None and toolbar.IsShown())

    def OnToolBarMenu(self, event, _id):
        if event.Checked():
            toolbar = self._app.GetToolbar(self, _id, create=True)
            aui_pane = self.auimgr.GetPane(toolbar)
            aui_pane.Show()
        else:
            toolbar = self._app.GetToolbar(self, _id, create=False)
            if toolbar is not None:
                aui_pane = self.auimgr.GetPane(toolbar)
                aui_pane.Hide()
        self.auimgr.Update()

    def OnPanelMenu(self, event, _id):
        '''Called when the user toggles something on the panel menu'''
        if event.Checked():
            self.ShowPanels(_id)
        else:
            # FIXME: I don't think this works if the id represents
            # a tab on the shelf. 
            panel = self._app.GetPanel(self, _id, create=False)
            if panel is not None:
                aui_pane = self.auimgr.GetPane(panel)
                aui_pane.Hide()
        self.auimgr.Update()
        wx.CallAfter(self.SaveCurrentPerspective)

    def OnChangePerspective(self, event):
        self.SaveCurrentPerspective()
        id_ = event.GetId()
        print "ChangePerspective; id=", id_
        self.LoadPerspective(event.GetId())

